package com.hjy.backend.dataManager.pageCache;

import com.hjy.backend.commons.AbstractCache;
import com.hjy.backend.dataManager.page.Page;
import com.hjy.backend.dataManager.page.PageImpl;
import com.hjy.common.error.Error;
import com.hjy.backend.utils.Panic;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PageCacheImpl extends AbstractCache<Page> implements PageCache {

    // 最少也需要缓存10个资源
    private static final int MEM_MIN_LIM = 10;
    public static final String DB_SUFFIX = ".db";

    private RandomAccessFile file;
    private FileChannel fc;
    private Lock fileLock;

    private AtomicInteger pageNumbers; // 用于记录页面的数量

    PageCacheImpl(RandomAccessFile file, FileChannel fileChannel, int maxResource) {
        super(maxResource);
        // 判断一下可以缓存的资源数是不是太少了
        if(maxResource < MEM_MIN_LIM) {
            Panic.panic(Error.MemTooSmallException);
        }
        long length = 0;
        try {
            length = file.length();
        } catch (IOException e) {
            Panic.panic(e);
        }
        this.file = file;
        this.fc = fileChannel;
        this.fileLock = new ReentrantLock();
        this.pageNumbers = new AtomicInteger((int)length / PAGE_SIZE);
    }

    /**
     * 创建一个新的页面
     *
     * @param initData
     * @return
     */
    @Override
    public int newPage(byte[] initData) {
        int pageId = pageNumbers.incrementAndGet();
        Page pg = new PageImpl(pageId, initData, null);
        flush(pg);
        return pageId;
    }

    /**
     * 获取数据页
     *
     * @param pageId
     * @return
     * @throws Exception
     */
    @Override
    public Page getPage(int pageId) throws Exception {
        // 从缓存中获取数据页
        return get((long) pageId);
    }

    /**
     * 从数据页文件 xxx.db 中获取页数据, 并且封装成 Page
     *
     * @param key
     * @return
     * @throws Exception
     */
    @Override
    protected Page getForCache(long key) throws Exception {
        int pageId = (int) key;
        // 获取当前页面在数据页文件的相对位置
        long offset = PageCacheImpl.pageOffset(pageId);
        ByteBuffer buf = ByteBuffer.allocate(PAGE_SIZE);
        fileLock.lock();

        try {
            fc.position(offset);
            fc.read(buf);
        } catch (IOException e) {
            Panic.panic(e);
        }

        fileLock.unlock();
        return new PageImpl(pageId, buf.array(), this);
    }

    /**
     * 将内存中的脏数据页写进磁盘
     *
     * @param pg
     */
    @Override
    protected void releaseForCache(Page pg) {
        if (pg.isDirty()) {
            flush(pg);
            pg.setDirty(false);
        }
    }

    /**
     * 从缓存中释放页面
     *
     * @param page
     */
    @Override
    public void release(Page page) {
        release((long) page.getPageNumber());
    }

    /**
     * 截断数据文件
     *
     * @param maxPageId
     */
    @Override
    public void truncateByPageId(int maxPageId) {
        long size = pageOffset(maxPageId + 1);
        try {
            file.setLength(size);
        } catch (IOException e) {
            Panic.panic(e);
        }
        pageNumbers.set(maxPageId);
    }

    /**
     * 获取当前最后一个页面的序号
     *
     * @return
     */
    @Override
    public int getPageNumber() {
        return pageNumbers.intValue();
    }

    @Override
    public void flushPage(Page pg) {
        flush(pg);
    }

    @Override
    public void close() {
        super.close();
        try {
            fc.close();
            file.close();
        } catch (Exception e) {
            Panic.panic(e);
        }
    }

    /**
     * 当前数据页相对数据文件 xx.db 的偏移地址
     *
     * @param pageId
     * @return
     */
    private static long pageOffset(int pageId) {
        return (long) (pageId - 1) * PAGE_SIZE;
    }

    /**
     * 将当前页面写进磁盘
     *
     * @param pg
     */
    private void flush(Page pg) {
        int pageId = pg.getPageNumber();
        long offset = pageOffset(pageId);

        fileLock.lock();
        try {
            ByteBuffer buffer = ByteBuffer.wrap(pg.getData());
            fc.position(offset);
            fc.write(buffer);
            fc.force(false);
        } catch (IOException e) {
            Panic.panic(e);
        } finally {
            fileLock.unlock();
        }
    }
}
